<?php

namespace Mini;

use Fiber;
use RuntimeException;

class AsyncHttpClient
{
    protected array $sockets = [];
    protected array $fibers = [];
    protected int $timeout; // 毫秒级超时时间

    public function __construct(int $timeout = 5000)
    {
        $this->timeout = $timeout;
    }

    public function get(string $url): Promise
    {
        return new Promise(function ($resolve, $reject) use ($url) {
            $fiber = new Fiber(function () use ($url, $resolve, $reject, &$fiber) {
                try {
                    echo "开始请求: $url\n";

                    $parts = parse_url($url);
                    if (!$parts || !isset($parts['host'])) {
                        throw new RuntimeException("无效的URL: $url");
                    }

                    $host = $parts['host'];
                    $port = ($parts['scheme'] ?? 'http') === 'https' ? 443 : 80;
                    $path = $parts['path'] ?? '/';

                    $socket = stream_socket_client(
                        "$host:$port",
                        $errno,
                        $errstr,
                        5,
                        STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT
                    );

                    if (!$socket) {
                        throw new RuntimeException("连接失败: $errstr ($errno)");
                    }

                    stream_set_blocking($socket, false);

                    $id = (int)$socket;

                    $this->sockets[$id] = [
                        'socket' => $socket,
                        'fiber' => $fiber,
                        'resolve' => $resolve,
                        'reject' => $reject,
                        'status' => 'connect', // 初始是等待连接
                        'request' => "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n",
                        'response' => '',
                        'start_time' => microtime(true),
                    ];

                    $this->fibers[$id] = $fiber;

                    Fiber::suspend(); // 第一次挂起：等待连接完成

                    Fiber::suspend(); // 第二次挂起：等待响应读取完成

                    // 恢复后，返回响应内容
                    $response = $this->sockets[$id]['response'] ?? '';
                    $resolve($response);

                    fclose($socket);
                    unset($this->sockets[$id], $this->fibers[$id]);

                } catch (\Throwable $e) {
                    $reject($e);
                }
            });

            $fiber->start();
        });
    }

    public function run(): void
    {
        while (!empty($this->sockets)) {
            $read = [];
            $write = [];
            $except = null;

            foreach ($this->sockets as $info) {
                $socket = $info['socket'];
                if ($info['status'] === 'connect') {
                    $write[] = $socket;
                } else {
                    $read[] = $socket;
                }
            }

            if (empty($read) && empty($write)) {
                throw new RuntimeException('没有可操作的流.');
            }

            $timeoutSec = intdiv($this->timeout, 1000);
            $timeoutUsec = ($this->timeout % 1000) * 1000;

            $numChanged = stream_select($read, $write, $except, $timeoutSec, $timeoutUsec);

            if ($numChanged === false) {
                throw new RuntimeException('stream_select() 失败');
            }

            // 检查超时
            $now = microtime(true);
            foreach ($this->sockets as $id => $info) {
                if (($now - $info['start_time']) * 1000 > $this->timeout) {
                    echo "请求超时: socket $id\n";
                    if (isset($info['reject'])) {
                        $info['reject'](new RuntimeException('请求超时'));
                    }
                    if (is_resource($info['socket'])) {
                        fclose($info['socket']);
                    }
                    unset($this->sockets[$id], $this->fibers[$id]);
                }
            }

            foreach ($write as $socket) {
                $id = (int)$socket;
                if (isset($this->sockets[$id]) && $this->sockets[$id]['status'] === 'connect') {
                    echo "连接完成: socket $id\n";
                    fwrite($socket, $this->sockets[$id]['request']);
                    $this->sockets[$id]['status'] = 'read';
                    $fiber = $this->sockets[$id]['fiber'];
                    if ($fiber && $fiber->isSuspended()) {
                        $fiber->resume();
                    }
                }
            }

            foreach ($read as $socket) {
                $id = (int)$socket;
                if (isset($this->sockets[$id]) && $this->sockets[$id]['status'] === 'read') {
                    echo "读取数据: socket $id\n";
                    $data = stream_get_contents($socket);

                    if ($data !== false) {
                        $this->sockets[$id]['response'] .= $data;
                    }

                    $fiber = $this->sockets[$id]['fiber'];
                    if ($fiber && $fiber->isSuspended()) {
                        $fiber->resume();
                    }
                }
            }
        }
    }
}
